/*
 * Copyright (C) 2008-2015 Fuzhou Rockchip Electronics Co., Ltd
 *
 * Copyright (C) 2011 Google, Inc.
 *
 * Copyright (C) 2010 Texas Instruments
 *
 * Author : Mohammed Afzal M A <afzal@ti.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 *
 *
 * Fastboot is implemented using gadget stack, many of the ideas are
 * derived from fastboot implemented in OmapZoom by
 * Tom Rix <Tom.Rix@windriver.com>, and portion of the code has been
 * ported from OmapZoom.
 *
 * Part of OmapZoom was copied from Android project, Android source
 * (legacy bootloader) was used indirectly here by using OmapZoom.
 *
 * This is Android's Copyright:
 *
 * Copyright (C) 2008 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <common.h>
#include <command.h>
#include <malloc.h>
#include <fastboot.h>
#include <usbdevice.h>
#include <linux/sizes.h>
#ifdef CONFIG_RESOURCE_PARTITION
#include <resource.h>
#endif
#include <power/battery.h>
#include <power/pmic.h>
#include <fdtdec.h>
#include <usbdevice.h>

DECLARE_GLOBAL_DATA_PTR;

#include <../board/rockchip/common/config.h>

/* If a BUILD_TAG was passed as an argument to make, use it
 * for the fastboot version.  Otherwise, see if a board file
 * defined a CONFIG_FASTBOOT_VERSION_BOOTLOADER and if so, use
 * that.  Otherwise, use an automatically constructed one of the form:
 *    productnameYMDHB
 * where:
 *    productname is FASTBOOT_PRODUCT_NAME
 *    Y is the year with 'A' being 2011 and incrementing from there
 *    M is the month
 *    D is the day of the month
 *    H is the hour
 *    B is the minute divided by two
 * All of the fields are based upon the build time.  M, D, H and B are all
 * output in base 36 (i.e. each digit is in the set 0-9A-Z).  A quick and
 * dirty Python program to convert one of these versions back to the build
 * time:

#! /usr/bin/env python
import sys
foo = sys.argv[1][-5:]
print "%04d/%02d/%02d %02d:%02d\n" % (int(foo[0], 36) + 2001,
int(foo[1], 36),
int(foo[2], 36),
int(foo[3], 36),
int(foo[4], 36) * 2)
*/
#ifdef BUILD_TAG
#undef CONFIG_FASTBOOT_VERSION_BOOTLOADER
#define CONFIG_FASTBOOT_VERSION_BOOTLOADER BUILD_TAG
#else
#ifndef CONFIG_FASTBOOT_VERSION_BOOTLOADER
#include <generated/timestamp_autogenerated.h>
#define CONFIG_FASTBOOT_VERSION_BOOTLOADER \
	(FASTBOOT_PRODUCT_NAME)
#endif
#endif

#define FASTBOOT_RUN_RECOVERY_ENV_NAME "fastboot_run_recovery"
#define FASTBOOT_UNLOCK_TIMEOUT_SECS 5

#include <exports.h>
#include <environment.h>

/* USB specific */

#include <usb_defs.h>
#include <usb/udc.h>

#if defined(CONFIG_PPC)
#include <usb/mpc8xx_udc.h>
#elif defined(CONFIG_OMAP1510)
#include <usb/omap1510_udc.h>
#elif defined(CONFIG_MUSB_UDC)
#include <usb/musb_udc.h>
#elif defined(CONFIG_PXA27X)
#include <usb/pxa27x_udc.h>
#elif defined(CONFIG_SPEAR3XX) || defined(CONFIG_SPEAR600)
#include <usb/spr_udc.h>
#elif defined(CONFIG_RK_UDC)
#include <usb/dwc_otg_udc.h>
#elif defined(CONFIG_RK_DWC3_UDC)
#include <dwc3-uboot.h>
#include <usb/dwc3_rk_udc.h>
#endif

#if defined (CONFIG_OMAP)
#include <asm/arch/sys_proto.h>
#endif

#define STR_LANG		0x00
#define STR_MANUFACTURER	0x01
#define STR_PRODUCT		0x02
#define STR_SERIAL		0x03
#define STR_CONFIGURATION	0x04
#define STR_INTERFACE		0x05
#define STR_COUNT		0x06

#define CONFIG_USBD_CONFIGURATION_STR	"Android Fastboot Configuration"
#define CONFIG_USBD_INTERFACE_STR	"Android Fastboot Interface"

#define USBFBT_BCD_DEVICE	0x00
#define	USBFBT_MAXPOWER		0x32

#define USB_FLUSH_DELAY_MICROSECS 1000

#define	NUM_CONFIGS	1
#define	NUM_INTERFACES	1
#define	NUM_ENDPOINTS	2
#ifdef CONFIG_RK_DWC3_UDC
	#define	RX_EP_INDEX	1
#else
	#define	RX_EP_INDEX	2
#endif

#define	TX_EP_INDEX	1

struct _fbt_config_desc {
	struct usb_configuration_descriptor configuration_desc;
	struct usb_interface_descriptor interface_desc;
	struct usb_endpoint_descriptor endpoint_desc[NUM_ENDPOINTS];
};

static void fbt_handle_response(void);

#ifdef CONFIG_RK_UDC
static u8 fastboot_name[] = "rk_fastboot";
#endif
/* defined and used by gadget/ep0.c */
extern struct usb_string_descriptor **usb_strings;

/* USB Descriptor Strings */
static char serial_number[33]; /* what should be the length ?, 33 ? */
static __attribute__ ((aligned(4))) u8 wstr_lang[4] = {4, USB_DT_STRING, 0x9, 0x4};
static __attribute__ ((aligned(4))) u8 wstr_manufacturer[2 + 2*(sizeof(CONFIG_USBD_MANUFACTURER)-1)];
static __attribute__ ((aligned(4))) u8 wstr_product[2 + 2*(sizeof(CONFIG_USBD_PRODUCT_NAME)-1)];
static __attribute__ ((aligned(4))) u8 wstr_serial[2 + 2*(sizeof(serial_number) - 1)];
static __attribute__ ((aligned(4))) u8 wstr_configuration[2 + 2*(sizeof(CONFIG_USBD_CONFIGURATION_STR)-1)];
static __attribute__ ((aligned(4))) u8 wstr_interface[2 + 2*(sizeof(CONFIG_USBD_INTERFACE_STR)-1)];

/* USB descriptors */
static struct usb_device_descriptor device_descriptor = {
	.bLength = sizeof(struct usb_device_descriptor),
	.bDescriptorType =	USB_DT_DEVICE,
	.bcdUSB =		cpu_to_le16(USB_BCD_VERSION),
	.bDeviceClass =		0x00,
	.bDeviceSubClass =	0x00,
	.bDeviceProtocol =	0x00,
	.bMaxPacketSize0 =	EP0_MAX_PACKET_SIZE,
	.idVendor =		cpu_to_le16(CONFIG_USBD_VENDORID),
	.idProduct =		cpu_to_le16(CONFIG_USBD_PRODUCTID_FASTBOOT),
	.bcdDevice =		cpu_to_le16(USBFBT_BCD_DEVICE),
	.iManufacturer =	STR_MANUFACTURER,
	.iProduct =		STR_PRODUCT,
	.iSerialNumber =	STR_SERIAL,
	.bNumConfigurations =	NUM_CONFIGS
};

static struct _fbt_config_desc fbt_config_desc = {
	.configuration_desc = {
		.bLength = sizeof(struct usb_configuration_descriptor),
		.bDescriptorType = USB_DT_CONFIG,
		.wTotalLength =	cpu_to_le16(sizeof(struct _fbt_config_desc)),
		.bNumInterfaces = NUM_INTERFACES,
		.bConfigurationValue = 1,
		.iConfiguration = STR_CONFIGURATION,
		.bmAttributes =	BMATTRIBUTE_SELF_POWERED | BMATTRIBUTE_RESERVED,
		.bMaxPower = USBFBT_MAXPOWER,
	},
	.interface_desc = {
		.bLength  = sizeof(struct usb_interface_descriptor),
		.bDescriptorType = USB_DT_INTERFACE,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 0,
		.bNumEndpoints = 0x2,
		.bInterfaceClass = FASTBOOT_INTERFACE_CLASS,
		.bInterfaceSubClass = FASTBOOT_INTERFACE_SUB_CLASS,
		.bInterfaceProtocol = FASTBOOT_INTERFACE_PROTOCOL,
		.iInterface = STR_INTERFACE,
	},
	.endpoint_desc = {
		{
			.bLength = sizeof(struct usb_endpoint_descriptor),
			.bDescriptorType = USB_DT_ENDPOINT,
			/* XXX: can't the address start from 0x1, currently
			   seeing problem with "epinfo" */
			.bEndpointAddress = RX_EP_INDEX | USB_DIR_OUT,
			.bmAttributes =	USB_ENDPOINT_XFER_BULK,
			.wMaxPacketSize = 0x200,
			.bInterval = 0xFF,
		},
		{
			.bLength = sizeof(struct usb_endpoint_descriptor),
			.bDescriptorType = USB_DT_ENDPOINT,
			/* XXX: can't the address start from 0x1, currently
			   seeing problem with "epinfo" */
			.bEndpointAddress = TX_EP_INDEX | USB_DIR_IN,
			.bmAttributes = USB_ENDPOINT_XFER_BULK,
			.wMaxPacketSize = 0x200,
			.bInterval = 0xFF,
		},
	},
};
#ifdef CONFIG_RK_UDC
static struct usb_interface_descriptor interface_descriptors[NUM_INTERFACES];
static struct usb_endpoint_descriptor *ep_descriptor_ptrs[NUM_ENDPOINTS];
#endif

static struct usb_string_descriptor *fbt_string_table[STR_COUNT];

#ifdef CONFIG_RK_UDC
static struct usb_device_instance device_instance[1];
static struct usb_bus_instance bus_instance[1];
static struct usb_configuration_instance config_instance[NUM_CONFIGS];
static struct usb_interface_instance interface_instance[NUM_INTERFACES];
static struct usb_alternate_instance alternate_instance[NUM_INTERFACES];
static struct usb_endpoint_instance endpoint_instance[NUM_ENDPOINTS + 1];
#endif
/* FASBOOT specific */

/* U-boot version */
extern char version_string[];

static struct cmd_fastboot_interface priv;

#ifdef CONFIG_FASTBOOT_LOG
static char* log_buffer;
static uint32_t log_position;
#endif

//TODO: get rid of these!
extern void resume_usb(struct usb_endpoint_instance *endpoint, int max_size);
extern void suspend_usb(void);
extern int is_usbd_high_speed(void);
extern void board_fbt_run_recovery_wipe_data(void);

#ifdef CONFIG_RK_UDC
static void fbt_init_endpoints(void);
#endif

static void fbt_init_strings(void);

#ifdef CONFIG_RK_DWC3_UDC

int fbt_write_bulk_ep(uint32_t nLen, void *buf)
{
	priv.tx_giveback.status = SEND_IN_PROGRESS;
	priv.tx_giveback.actual = 0;
	priv.tx_giveback.buf = NULL;
	return RK_Dwc3WriteBulkEndpoint(nLen, buf);
}

int fbt_read_bulk_ep(uint32_t nLen, void *buf)
{
	priv.rx_giveback.status = RECV_READY;
	priv.rx_giveback.actual = 0;
	priv.rx_giveback.buf = NULL;
	return RK_Dwc3ReadBulkEndpoint(nLen, buf);
}

void fbt_event_handler_for_dwc3(int nEvent)
{
	int event = nEvent & 0xffff;
	FBTINFO("@fbt_event_handler_for_dwc3   %d\n", event);
	switch (event) {
	case 1:/* Device_Reset */
	case 5:/* Device_Disconnect */
		priv.configured = 0;
		break;
	case 2:/* DEVICE_CONFIGURED */
		priv.configured = 1;
		priv.txbuf_num = 0;
		priv.rxbuf_num = 0;
		priv.tx_giveback.status = SEND_FINISHED_OK;
		priv.tx_giveback.actual = 0;
		fbt_read_bulk_ep(FASTBOOT_COMMAND_SIZE, priv.rx_buffer);
		break;
	case 3:/* DEVICE_ADDRESS_ASSIGNED */
		break;
	case 4:/* DEVICE_CLEAR_FEATURE */
	default:
		break;
	}
}

void fbt_dwc3_rx_handler(int status, uint32_t actual, void *buf)
{
	if (status) {
		priv.rx_giveback.status = RECV_ERROR;
	} else {
		priv.rx_giveback.status = RECV_OK;
		priv.rx_giveback.actual = actual;
		priv.rx_giveback.buf = buf;
	}
}

void fbt_dwc3_tx_handler(int status, uint32_t actual, void *buf)
{
	if (status) {
		priv.tx_giveback.status = SEND_FINISHED_ERROR;
	} else {
		priv.tx_giveback.status = SEND_FINISHED_OK;
		priv.tx_giveback.actual = actual;
		priv.tx_giveback.buf = buf;
	}
}

void fbt_init_dwc3_udc_instance(struct rk_dwc3_udc_instance *instance)
{
	int i;
	memset(instance, 0, sizeof(*instance));
	instance->device_desc = &device_descriptor;
	instance->config_desc = (void *)&fbt_config_desc;
	instance->interface_desc = &fbt_config_desc.interface_desc;
	for (i = 0; i < NUM_ENDPOINTS; i++) {
		instance->endpoint_desc[i] = &fbt_config_desc.endpoint_desc[i];
		instance->rx_buffer[i] = priv.rx_buffer;
		instance->tx_buffer[i] = priv.tx_buffer;
	}
	instance->rx_buffer_size = 512;
	instance->tx_buffer_size = 512;
	fbt_init_strings();
	for (i = 0; i < STR_COUNT; i++)
		instance->string_desc[i] = fbt_string_table[i];
	instance->device_event = fbt_event_handler_for_dwc3;
	instance->rx_handler = fbt_dwc3_rx_handler;
	instance->tx_handler = fbt_dwc3_tx_handler;
}
#endif


/* USB specific */

/* utility function for converting char * to wide string used by USB */
static void str2wide(char *str, u16 * wide)
{
	int i;
	for (i = 0; i < strlen(str) && str[i]; i++) {
#if defined(__LITTLE_ENDIAN)
		wide[i] = (u16) str[i];
#elif defined(__BIG_ENDIAN)
		wide[i] = ((u16)(str[i])<<8);
#else
#error "__LITTLE_ENDIAN or __BIG_ENDIAN undefined"
#endif
	}
}

/* fastboot_init has to be called before this fn to get correct serial string */
static void fbt_init_strings(void)
{
	struct usb_string_descriptor *string;

	fbt_string_table[STR_LANG] = (struct usb_string_descriptor *)wstr_lang;

	string = (struct usb_string_descriptor *) wstr_manufacturer;
	string->bLength = sizeof(wstr_manufacturer);
	string->bDescriptorType = USB_DT_STRING;
	str2wide(CONFIG_USBD_MANUFACTURER, string->wData);
	fbt_string_table[STR_MANUFACTURER] = string;

	string = (struct usb_string_descriptor *) wstr_product;
	string->bLength = sizeof(wstr_product);
	string->bDescriptorType = USB_DT_STRING;
	str2wide(CONFIG_USBD_PRODUCT_NAME, string->wData);
	fbt_string_table[STR_PRODUCT] = string;

	string = (struct usb_string_descriptor *) wstr_serial;
	string->bLength = sizeof(wstr_serial);
	string->bDescriptorType = USB_DT_STRING;
	str2wide(serial_number, string->wData);
	fbt_string_table[STR_SERIAL] = string;

	string = (struct usb_string_descriptor *) wstr_configuration;
	string->bLength = sizeof(wstr_configuration);
	string->bDescriptorType = USB_DT_STRING;
	str2wide(CONFIG_USBD_CONFIGURATION_STR, string->wData);
	fbt_string_table[STR_CONFIGURATION] = string;

	string = (struct usb_string_descriptor *) wstr_interface;
	string->bLength = sizeof(wstr_interface);
	string->bDescriptorType = USB_DT_STRING;
	str2wide(CONFIG_USBD_INTERFACE_STR, string->wData);
	fbt_string_table[STR_INTERFACE] = string;

	/* Now, initialize the string table for ep0 handling */
	usb_strings = fbt_string_table;
}
#ifdef CONFIG_RK_UDC
void fbt_receive_firstcmd(void)
{
	struct usb_endpoint_instance *ep = &endpoint_instance[1];

	// get first CMD

	ep->rcv_urb->buffer_length = FASTBOOT_COMMAND_SIZE;
	ep->rcv_urb->actual_length = 0;
	// make sure the endpoint re-enabled
	suspend_usb();
	resume_usb(ep, 0);
}

static void fbt_event_handler (struct usb_device_instance *device,
		usb_device_event_t event, int data)
{
	switch (event) {
		case DEVICE_RESET:
		case DEVICE_BUS_INACTIVE:
			priv.configured = 0;
			break;
		case DEVICE_CONFIGURED:
			priv.configured = 1;
			break;

		case DEVICE_ADDRESS_ASSIGNED:
			fbt_init_endpoints();
			fbt_receive_firstcmd();
		default:
			break;
	}
}

/* fastboot_init has to be called before this fn to get correct serial string */
static void fbt_init_instances(void)
{
	int i;

	/* initialize device instance */
	memset(device_instance, 0, sizeof(struct usb_device_instance));
	device_instance->name = (char*)fastboot_name;
	device_instance->device_state = STATE_INIT;
	device_instance->device_descriptor = &device_descriptor;
	device_instance->event = fbt_event_handler;
	device_instance->cdc_recv_setup = NULL;
	device_instance->bus = bus_instance;
	device_instance->configurations = NUM_CONFIGS;
	device_instance->configuration_instance_array = config_instance;

	/* XXX: what is this bus instance for ?, can't it be removed by moving
	   endpoint_array and serial_number_str is moved to device instance */
	/* initialize bus instance */
	memset(bus_instance, 0, sizeof(struct usb_bus_instance));
	bus_instance->device = device_instance;
	bus_instance->endpoint_array = endpoint_instance;
	/* XXX: what is the relevance of max_endpoints & maxpacketsize ? */
	bus_instance->max_endpoints = 1;
	bus_instance->maxpacketsize = 64;
	bus_instance->serial_number_str = serial_number;

	/* configuration instance */
	memset(config_instance, 0, sizeof(struct usb_configuration_instance));
	config_instance->interfaces = NUM_INTERFACES;
	config_instance->configuration_descriptor =
		(struct usb_configuration_descriptor *)&fbt_config_desc;
	config_instance->interface_instance_array = interface_instance;

	/* XXX: is alternate instance required in case of no alternate ? */
	/* interface instance */
	memset(interface_instance, 0, sizeof(struct usb_interface_instance));
	interface_instance->alternates = 1;
	interface_instance->alternates_instance_array = alternate_instance;

	/* alternates instance */
	memset(alternate_instance, 0, sizeof(struct usb_alternate_instance));
	alternate_instance->interface_descriptor = interface_descriptors;
	alternate_instance->endpoints = NUM_ENDPOINTS;
	alternate_instance->endpoints_descriptor_array = ep_descriptor_ptrs;

	/* endpoint instances */
	memset(endpoint_instance, 0, sizeof(endpoint_instance));
	endpoint_instance[0].endpoint_address = 0;
	endpoint_instance[0].rcv_packetSize = EP0_MAX_PACKET_SIZE;
	endpoint_instance[0].rcv_attributes = USB_ENDPOINT_XFER_CONTROL;
	endpoint_instance[0].tx_packetSize = EP0_MAX_PACKET_SIZE;
	endpoint_instance[0].tx_attributes = USB_ENDPOINT_XFER_CONTROL;
	/* XXX: following statement to done along with other endpoints
	   at another place ? */

	for (i = 1; i <= NUM_ENDPOINTS; i++) {
		endpoint_instance[i].endpoint_address =
			ep_descriptor_ptrs[i - 1]->bEndpointAddress;

		endpoint_instance[i].rcv_attributes =
			ep_descriptor_ptrs[i - 1]->bmAttributes;

		endpoint_instance[i].rcv_packetSize =
			le16_to_cpu(ep_descriptor_ptrs[i - 1]->wMaxPacketSize);

		endpoint_instance[i].tx_attributes =
			ep_descriptor_ptrs[i - 1]->bmAttributes;

		endpoint_instance[i].tx_packetSize =
			le16_to_cpu(ep_descriptor_ptrs[i - 1]->wMaxPacketSize);

		endpoint_instance[i].tx_attributes =
			ep_descriptor_ptrs[i - 1]->bmAttributes;

		urb_link_init(&endpoint_instance[i].rcv);
		urb_link_init(&endpoint_instance[i].rdy);
		urb_link_init(&endpoint_instance[i].tx);
		urb_link_init(&endpoint_instance[i].done);

		if (endpoint_instance[i].endpoint_address & USB_DIR_IN)
			endpoint_instance[i].tx_urb =
				usbd_alloc_urb(device_instance,
						&endpoint_instance[i]);
		else
			endpoint_instance[i].rcv_urb =
				usbd_alloc_urb(device_instance,
						&endpoint_instance[i]);
	}
}

/* XXX: ep_descriptor_ptrs can be removed by making better use of
   fbt_config_desc.endpoint_desc */
static void fbt_init_endpoint_ptrs(void)
{
	ep_descriptor_ptrs[0] = &fbt_config_desc.endpoint_desc[0];
	ep_descriptor_ptrs[1] = &fbt_config_desc.endpoint_desc[1];
}

static void fbt_init_endpoints(void)
{
	int i;

	/* XXX: should it be moved to some other function ? */
	bus_instance->max_endpoints = NUM_ENDPOINTS + 1;

	/* XXX: is this for loop required ?, yes for MUSB it is */
	for (i = 1; i <= NUM_ENDPOINTS; i++) {

		/* configure packetsize based on HS negotiation status */
		if (is_usbd_high_speed()) {
			FBTINFO("setting up HS USB device ep%x\n",
					endpoint_instance[i].endpoint_address);
			ep_descriptor_ptrs[i - 1]->wMaxPacketSize =
				CONFIG_USBD_FASTBOOT_BULK_PKTSIZE_HS;
		} else {
			FBTINFO("setting up FS USB device ep%x\n",
					endpoint_instance[i].endpoint_address);
			ep_descriptor_ptrs[i - 1]->wMaxPacketSize =
				CONFIG_USBD_FASTBOOT_BULK_PKTSIZE_FS;
		}

		endpoint_instance[i].tx_packetSize =
			le16_to_cpu(ep_descriptor_ptrs[i - 1]->wMaxPacketSize);
		endpoint_instance[i].rcv_packetSize =
			le16_to_cpu(ep_descriptor_ptrs[i - 1]->wMaxPacketSize);
	}
}

static struct urb *next_urb(struct usb_device_instance *device,
		struct usb_endpoint_instance *endpoint)
{
	struct urb *current_urb;
	int space;

	/* If there's a queue, then we should add to the last urb */
	if (!endpoint->tx_queue)
		current_urb = endpoint->tx_urb;
	else {
		/* Last urb from tx chain */
		current_urb = p2surround(struct urb, link, endpoint->tx.prev);
	}

	/* Make sure this one has enough room */
	space = current_urb->buffer_length - current_urb->actual_length;
	if (space > 0)
		return current_urb;
	else {		/* No space here */
		/* First look at done list */
		current_urb = first_urb_detached(&endpoint->done);
		if (!current_urb)
			current_urb = usbd_alloc_urb(device, endpoint);

		urb_append(&endpoint->tx, current_urb);
		endpoint->tx_queue++;
	}
	return current_urb;
}
#endif

static void fbt_wait_usb_fifo_flush(void)
{
	/* give time to flush FIFO and remote to receive data.
	 * otherwise, USB can get hung.  someday we might actually
	 * try checking USB fifo status directly but for now, just
	 * spin for some time.
	 */
	udelay(USB_FLUSH_DELAY_MICROSECS);
}

/* FASTBOOT specific */

/*
 * Android style flash utilties
 */
static void set_serial_number(const char *serial_no)
{
	strncpy(serial_number, serial_no, sizeof(serial_number));
	serial_number[sizeof(serial_number) - 1] = '\0';
	priv.serial_no = serial_number;
	//setenv("fbt_sn#", serial_number);
	FBTDBG("fastboot serial_number = %s\n", serial_number);
}

static void create_serial_number(void)
{
	char *sn = getenv("fbt_sn#");

	if (sn == NULL) {
		FBTDBG("Setting serial number from constant (no dieid info)\n");
		set_serial_number("0123456789");
	} else {
		FBTDBG("Setting serial number from unique id\n");
		set_serial_number(sn);
	}
}

static void fbt_set_unlocked(int unlocked)
{
	char *unlocked_string;

	FBTDBG("Setting device to %s\n",
			unlocked ? "unlocked" : "locked");
	priv.unlocked = unlocked;
	if (unlocked)
		unlocked_string = "1";
	else
		unlocked_string = "0";
	setenv(FASTBOOT_UNLOCKED_ENV_NAME, unlocked_string);
#if defined(CONFIG_CMD_SAVEENV)
	saveenv();
#endif
}

void fbt_fastboot_init(void)
{
	char *fastboot_unlocked_env;
	priv.flag = 0;
	priv.d_size = 0;
	priv.d_bytes = 0;
	priv.u_size = 0;
	priv.u_bytes = 0;
	priv.exit = 0;
	priv.unlock_pending_start_time = 0;

	priv.unlocked = 1;

	priv.transfer_buffer = priv.buffer[0] = (u8 *)gd->arch.fastboot_buf_addr;
	priv.buffer[1] = priv.buffer[0] + CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH;
	priv.transfer_buffer_size  = CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH;

#ifdef CONFIG_FASTBOOT_LOG
	log_buffer = (char*)gd->arch.fastboot_log_buf_addr;
#endif

	fastboot_unlocked_env = getenv(FASTBOOT_UNLOCKED_ENV_NAME);
	if (fastboot_unlocked_env) {
		unsigned long unlocked;
		if (!strict_strtoul(fastboot_unlocked_env, 10, &unlocked)) {
			if (unlocked)
				priv.unlocked = 1;
			else
				priv.unlocked = 0;
		} else {
			FBTERR("bad env setting %s of %s,"
					" initializing to locked\n",
					fastboot_unlocked_env,
					FASTBOOT_UNLOCKED_ENV_NAME);
			fbt_set_unlocked(0);
		}
	} else {
		FBTDBG("no existing env setting for %s\n",
				FASTBOOT_UNLOCKED_ENV_NAME);
		FBTDBG("creating one set to false\n");
		fbt_set_unlocked(0);
	}
	if (priv.unlocked)
		FBTDBG("Device is unlocked\n");
	else
		FBTDBG("Device is locked\n");

	//TODO:load device info, setup serial_number
	if (priv.serial_no == NULL)
		create_serial_number();

}

static int fbt_handle_erase(char *cmdbuf)
{
	const disk_partition_t *ptn;
	int err;
	char *partition_name = cmdbuf + 6;
	ptn = board_fbt_get_partition(partition_name);
	if (ptn == 0) {
		FBTERR("Partition %s does not exist\n", partition_name);
		sprintf(priv.response, "FAILpartition does not exist");
		return -1;
	}

	FBTDBG("Erasing partition '%s':\n", ptn->name);

	FBTDBG("\tstart blk %llu, blk_cnt %llu\n", ptn->start,
			ptn->size);

	err = board_fbt_handle_erase(ptn);
	if (err) {
		FBTERR("Erasing '%s' FAILED! error=%d\n", ptn->name, err);
		sprintf(priv.response,
				"FAILfailed to erase partition (%d)", err);
	} else {
		FBTDBG("partition '%s' erased\n", ptn->name);
		sprintf(priv.response, "OKAY");
	}
	return err;
}

static int fbt_handle_flash(char *cmdbuf, int check_unlock)
{
	const disk_partition_t *ptn;
	const char *name = cmdbuf + 6;

	if (check_unlock && !priv.unlocked) {
		FBTERR("%s: failed, device is locked\n", __func__);
		sprintf(priv.response, "FAILdevice is locked");
		return -1;
	}

	if (!priv.d_bytes) {
		FBTERR("%s: failed, no image downloaded\n", __func__);
		sprintf(priv.response, "FAILno image downloaded");
		return -1;
	}

	ptn = board_fbt_get_partition(name);
	if (ptn)
		FBTDBG("name=%s,start=%llx,size=%llx\n", ptn->name, ptn->start, ptn->size);
	if (!board_fbt_handle_flash(name, ptn, &priv)) {
		sprintf(priv.response, "OKAY");

		//reload partition after flash.
		(void)board_fbt_load_partition_table();

		return 0;
	}
	sprintf(priv.response,
			"FAILWrite partition:%s", name);
	return -1;
}

struct getvar_entry {
	const char *variable_name;
	int exact_match;
	const char *(*getvar_func)(const char *arg);
};
extern char bootloader_ver[];

static const char *getvar_version(const char *unused)
{
	//return version_string;
	return bootloader_ver;
}

static const char *getvar_version_baseband(const char *unused)
{
	return "n/a";
}

static const char *getvar_version_bootloader(const char *unused)
{
	return CONFIG_FASTBOOT_VERSION_BOOTLOADER;
}

static const char *getvar_unlocked(const char *unused)
{
	return (priv.unlocked ? "yes" : "no");
}

static const char *getvar_secure(const char *unused)
{
	/* we use the inverse meaning of unlocked */
	return (priv.unlocked ? "no" : "yes");
}

static const char *getvar_product(const char *unused)
{
	return FASTBOOT_PRODUCT_NAME;
}

static const char *getvar_serialno(const char *unused)
{
	return priv.serial_no;
}

static const char *getvar_partition_type(const char *args)
{
	const char *partition_name;
	const disk_partition_t *ptn;

	priv.pending_ptn = NULL;

	partition_name = args + sizeof("partition-type:") - 1;
	snprintf(priv.pending_ptn_name, sizeof(priv.pending_ptn_name), "%s", partition_name);

	ptn = board_fbt_get_partition(partition_name);
	if (ptn) {
		FBTDBG("fastboot pending_ptn:%s\n", ptn->name);
		priv.pending_ptn = ptn;
		return (char*)ptn->type;
	}
	snprintf(priv.response, sizeof(priv.response),
			"FAILunknown partition %s\n", partition_name);
	return NULL;
}

static const char *getvar_checksum(const char *args)
{
#ifndef CONFIG_ROCKCHIP
	return NULL;
#else
	//1:parse params.
	uint32_t offset = 0;
	uint32_t blocks = 0;
	const char* buf = args + sizeof("checksum:") - 1;
	offset = simple_strtoull(buf, (char**)(&buf), 0);
	//skip a space char.
	buf++;
	blocks = simple_strtoull(buf, (char**)(&buf), 0);

	if (offset < 0 || blocks <= 0) {
		snprintf(priv.response, sizeof(priv.response),
				"FAILinvalidate params!\n");
		return NULL;
	}
	FBTDBG("try to get checksum, offset:0x%08x, blocks:0x%08x\n",
			offset, blocks);

	//2:get checksum for each parts
	buf = (const char*)priv.buffer[0];

	//may overflow?
	uint16_t buf_blocks = priv.transfer_buffer_size / RK_BLK_SIZE;

#ifndef CONFIG_QUICK_CHECKSUM
	uint32_t* crc_array = (uint32_t*) priv.buffer[1];
	uint16_t crc_counts = 0;
	uint32_t checksum = 0;
#else
	uint64_t checksum = 0;
#endif

	while (blocks > 0) {
		uint16_t read_blocks = blocks > buf_blocks? buf_blocks : blocks;

		if (StorageReadLba(offset, (void*)buf, read_blocks) != 0) {
			FBTERR("read failed, offset:0x%08x, blocks:0x%08x\n",
					offset, read_blocks);
			snprintf(priv.response, sizeof(priv.response),
					"FAILread 0x%08x failed!\n", offset);
			return NULL;
		}
		offset += read_blocks;
		blocks -= read_blocks;
#ifndef CONFIG_QUICK_CHECKSUM
		crc_array[crc_counts] = crc32(0, buf, read_blocks * RK_BLK_SIZE);
		FBTDBG("offset:0x%08x, blocks:0x%08x, crc:0x%08lx\n",
				offset, read_blocks, crc_array[crc_counts]);
		crc_counts++;
#else
		int i = 0;
		uint32_t* data = (uint32_t*) buf;
		for (i = 0;i < read_blocks * RK_BLK_SIZE >> 2;i++)
			checksum += data[i];
		FBTDBG("offset:0x%08x, blocks:0x%08x, checksum:0x%016llx\n",
				offset, read_blocks, checksum);
#endif
	}

#ifndef CONFIG_QUICK_CHECKSUM
	//3:compute whole checksum
	checksum = (crc_counts == 1)? crc_array[0] :
		crc32(0, (unsigned char*)crc_array, sizeof(uint32_t) * crc_counts);
	FBTDBG("whole checksum:0x%08lx\n", checksum);
#else
	FBTDBG("whole checksum:0x%016llx\n", checksum);
#endif

#ifndef CONFIG_QUICK_CHECKSUM
	snprintf(priv.response, sizeof(priv.response),
			"OKAY0x%08lx\n", checksum);
#else
	snprintf(priv.response, sizeof(priv.response),
			"OKAY0x%016llx\n", checksum);
#endif
	return NULL;
#endif
}

static const char *getvar_partition_offset(const char *args)
{
	const char *partition_name;
	const disk_partition_t *ptn;

	partition_name = args + sizeof("partition-offset:") - 1;
	ptn = board_fbt_get_partition(partition_name);
	if (ptn) {
		snprintf(priv.response, sizeof(priv.response),
				"OKAY0x%08llx", (uint64_t)ptn->start);
	} else {
		snprintf(priv.response, sizeof(priv.response),
				"FAILunknown partition %s\n", partition_name);
	}
	return NULL;
}


static const char *getvar_partition_size(const char *args)
{
	const char *partition_name;
	const disk_partition_t *ptn;

	partition_name = args + sizeof("partition-size:") - 1;
	ptn = board_fbt_get_partition(partition_name);
	if (ptn) {
		snprintf(priv.response, sizeof(priv.response),
				"OKAY0x%016llx(blk)", (uint64_t)ptn->size);
	} else {
		snprintf(priv.response, sizeof(priv.response),
				"FAILunknown partition %s\n", partition_name);
	}
	return NULL;
}

static const struct getvar_entry getvar_table[] = {
	{"version", 1, getvar_version},
	{"version-baseband", 1, getvar_version_baseband},
	{"version-bootloader", 1, getvar_version_bootloader},
	{"unlocked", 1, getvar_unlocked},
	{"secure", 1, getvar_secure},
	{"product", 1, getvar_product},
	{"serialno", 1, getvar_serialno},
	{"partition-type:", 0, getvar_partition_type},
	{"partition-size:", 0, getvar_partition_size},
	{"partition-offset:", 0, getvar_partition_offset},
	{"checksum:", 0, getvar_checksum}
};

static void fbt_handle_getvar(char *cmdbuf)
{
	char *subcmd = cmdbuf + sizeof("getvar:") - 1;
	const char *value = NULL;
	int do_all;
	int i;
	if (!strcmp(subcmd, "all"))
		do_all = 1;
	else
		do_all = 0;

	if (do_all) {
		for (i = 0; i < ARRAY_SIZE(getvar_table); i++) {
			value = (getvar_table[i].getvar_func)(subcmd);
			if (value ) {
				FBTDBG("%s: %s\n",
						getvar_table[i].variable_name, value);
			}
		}
		strcpy(priv.response, "OKAY");
	} else {
		for (i = 0; i < ARRAY_SIZE(getvar_table); i++) {
			int match;
			if (getvar_table[i].exact_match) {
				/* look for exact string match */
				match = !strcmp(getvar_table[i].variable_name,
						subcmd);
			} else {
				/* look for the target string at the
				 * beginning of the argument passed
				 */
				match = strstr(subcmd,
						getvar_table[i].variable_name) ==
					subcmd;
			}
			if (match) {
				value = (getvar_table[i].getvar_func)(subcmd);
				if (value == NULL) {
					/* handler did it all in terms of
					 * creating a response.
					 */
					return;
				}
				/* fall through to let the common code
				 * handle creating a response string.
				 */
				break;
			}
		}
#ifdef CONFIG_FASTBOOT_UBOOT_GETVAR
		if (!value) {
			ENTRY e, *ep;

			e.key = subcmd;
			e.data = NULL;
			ep = NULL;
			if (hsearch_r(e, FIND, &ep, &env_htab) && ep != NULL)
				value = ep->data;
		}
#endif
		if (value) {
			/* At first I was reluctant to use strncpy because it
			 * typically pads the whole buffer with nulls, but
			 * U-Boot's strncpy does not do that.  However, I
			 * do rely on priv.null_term after priv.response
			 * in the struct cmd_fastboot_interface to ensure
			 * the strlen in fbt_response_process doesn't take
			 * a long time.
			 */
			strcpy(priv.response, "OKAY");
			strncpy(priv.response + 4, value,
					(sizeof(priv.response) - 4));
		} else {
			strcpy(priv.response, "FAILunknown variable");
		}
	}
}

static void fbt_handle_reboot(const char *cmdbuf)
{
	if (!strcmp(&cmdbuf[6], "-bootloader")) {
		FBTDBG("%s\n", cmdbuf);
		board_fbt_set_reboot_type(FASTBOOT_REBOOT_BOOTLOADER);
	}
	if (!strcmp(&cmdbuf[6], "-recovery")) {
		FBTDBG("%s\n", cmdbuf);
		board_fbt_set_reboot_type(FASTBOOT_REBOOT_RECOVERY);
	}
	if (!strcmp(&cmdbuf[6], "-recovery:wipe_data")) {
		FBTDBG("%s\n", cmdbuf);
		board_fbt_set_reboot_type(FASTBOOT_REBOOT_RECOVERY_WIPE_DATA);
	}

	strcpy(priv.response, "OKAY");
	priv.flag |= FASTBOOT_FLAG_RESPONSE;
	fbt_handle_response();
	udelay(1000000); /* 1 sec */

	do_reset(NULL, 0, 0, NULL);
}

static int fbt_send_raw_info(const char *info, int bytes_left)
{
	int response_max;

	if (!priv.executing_command)
		return -1;

	/* break up info into response sized chunks */
	/* remove trailing '\n' */
	if (info[bytes_left-1] == '\n')
		bytes_left--;

	/* -4 for the INFO prefix */
	response_max = sizeof(priv.response) - 4;
	strcpy(priv.response, "INFO");
	while (1) {
		if (bytes_left >= response_max) {
			strncpy(priv.response + 4, info,
					response_max);

			/* flush any data set by command */
			priv.flag |= FASTBOOT_FLAG_RESPONSE;
			fbt_handle_response();
			fbt_wait_usb_fifo_flush();

			info += response_max;
			bytes_left -= response_max;
		} else {
			strncpy(priv.response + 4, info,
					bytes_left);

			/* in case we stripped '\n',
			   make sure priv.response is
			   terminated */
			priv.response[4 + bytes_left] = '\0';

			break;
		}
	}

	priv.flag |= FASTBOOT_FLAG_RESPONSE;
	fbt_handle_response();
	fbt_wait_usb_fifo_flush();

	return 0;
}

#ifdef CONFIG_FASTBOOT_LOG
static void fbt_dump_log(char *buf, uint32_t buf_size)
{
	/* the log consists of a bunch of printf output, with
	 * logs of '\n' interspersed. to make it format a
	 * bit better when sending it via the INFO
	 * part of the fastboot protocol, which has a limited
	 * buffer, break the log into bits that end
	 * with '\n', like replaying the printfs.
	 */
	char *line_start = buf;

	if (buf_size == 0) {
		FBTERR("%s: unexpected buf size of 0\n", __func__);
		return;
	}

	/* guarantee null termination for strchr/strlen */
	buf[buf_size] = 0;
	while (buf_size) {
		char *next_line  = strchr(line_start, '\n');
		if (next_line) {
			int len = next_line - line_start + 1;
			fbt_send_raw_info(line_start, len);
			line_start += len;
			buf_size -= len;
		} else {
			fbt_send_raw_info(line_start, strlen(line_start));
			break;
		}
	}
}
#endif //CONFIG_FASTBOOT_LOG

static void fbt_handle_oem(char *cmdbuf)
{
	cmdbuf += 4;

#ifdef CONFIG_FASTBOOT_LOG
	/* %fastboot oem log */
	if (strcmp(cmdbuf, "log") == 0) {
		FBTDBG("oem %s\n", cmdbuf);
		fbt_dump_log(log_buffer, log_position);
		strcpy(priv.response, "OKAY");
		return;
	}
#endif //CONFIG_FASTBOOT_LOG

	/* %fastboot oem recovery */
	if (strcmp(cmdbuf, "recovery") == 0) {
		FBTDBG("oem recovery\n");
		fbt_handle_reboot("reboot-recovery");
		return;
	}

	/* %fastboot oem recovery:wipe_data */
	if (strcmp(cmdbuf, "recovery:wipe_data") == 0) {
		FBTDBG("oem recovery:wipe_data\n");
		fbt_handle_reboot("reboot-recovery:wipe_data");
		return;
	}

	/* %fastboot oem unlock */
	if (strcmp(cmdbuf, "unlock") == 0) {
		FBTDBG("oem unlock\n");
		if (priv.unlocked) {
			FBTDBG("oem unlock ignored, device already unlocked\n");
			strcpy(priv.response, "FAILalready unlocked");
			return;
		}
		FBTDBG("oem unlock requested:\n");
		FBTDBG("\tUnlocking forces a factory reset and could\n");
		FBTDBG("\topen your device up to a world of hurt.  If you\n");
		FBTDBG("\tare sure you know what you're doing, then accept\n");
		FBTDBG("\tin %d seconds via 'fastboot oem unlock_accept'.\n",
				FASTBOOT_UNLOCK_TIMEOUT_SECS);
		priv.unlock_pending_start_time = get_timer(0);
		strcpy(priv.response, "OKAY");
		return;
	}

	if (strcmp(cmdbuf, "unlock_accept") == 0) {
		int err = 0;
		FBTDBG("oem unlock_accept\n");
		if (!priv.unlock_pending_start_time) {
			FBTERR("oem unlock_accept ignored, not pending\n");
			strcpy(priv.response, "FAILoem unlock not requested");
			return;
		}
		priv.unlock_pending_start_time = 0;
		FBTDBG("Erasing userdata partition\n");
		const disk_partition_t* ptn;
		ptn = board_fbt_get_partition("userdata");
		if (ptn) {
			err = board_fbt_handle_erase(ptn);
		}
		if (err) {
			FBTERR("Erase failed with error %d\n", err);
			strcpy(priv.response, "FAILErasing userdata failed");
			return;
		}
		FBTDBG("Erasing succeeded\n");
		fbt_set_unlocked(1);
		strcpy(priv.response, "OKAY");
		priv.flag |= FASTBOOT_FLAG_RESPONSE;
		fbt_handle_response();
		udelay(1000000); /* 1 sec */
		/* now reboot into recovery to do a format of the
		 * userdata partition so it's ready to use on next boot
		 */
		board_fbt_run_recovery_wipe_data();
		return;
	}

	if (strcmp(cmdbuf, "lock") == 0) {
		FBTDBG("oem lock\n");
		if (!priv.unlocked) {
			FBTERR("oem lock ignored, already locked\n");
			strcpy(priv.response, "FAILalready locked");
			return;
		}
		fbt_set_unlocked(0);
		strcpy(priv.response, "OKAY");
		return;
	}

	/* %fastboot oem erase partition <numblocks>
	 * similar to 'fastboot erase' except an optional number
	 * of blocks can be passed to erase less than the
	 * full partition, for speed
	 */
	if (strncmp(cmdbuf, "erase ", 6) == 0) {
		FBTDBG("oem %s\n", cmdbuf);
		fbt_handle_erase(cmdbuf);
		return;
	}

	/* All other oem commands are not allowed if device is locked */
	if (!priv.unlocked) {
		sprintf(priv.response, "FAILdevice is locked");
		return;
	}

	/* %fastboot oem ucmd ... */
	if (strncmp(cmdbuf, "ucmd ", 5) == 0) {
		FBTDBG("oem %s\n", cmdbuf);
		cmdbuf += 5;

		if (run_command(cmdbuf, 0) < 0)
			strcpy(priv.response, "FAILcommand failed");
		else
			strcpy(priv.response, "OKAY");
		return;
	}

	/* %fastboot oem [xxx] */
	FBTDBG("oem %s\n", cmdbuf);
	if (board_fbt_oem(cmdbuf) >= 0) {
		strcpy(priv.response, "OKAY");
		return;
	}

	FBTDBG("\nfastboot: unsupported oem command %s\n", cmdbuf);
	strcpy(priv.response, "FAILinvalid command");
}

static void fbt_handle_boot(const char *cmdbuf)
{
	if (!priv.unlocked) {
		sprintf(priv.response, "FAILdevice is locked");
		return;
	}

	if ((priv.d_bytes) &&
			(CONFIG_FASTBOOT_MKBOOTIMAGE_PAGE_SIZE < priv.d_bytes)) {
		char start[32];
#ifdef CONFIG_CMD_BOOTRK
		char *bootrk[] = { "bootrk", start };
#endif
		char *go[]    = { "go",    start };

		/*
		 * Use this later to determine if a command line was passed
		 * for the kernel.
		 */
		sprintf(start, "%p", priv.transfer_buffer);

		/* Execution should jump to kernel so send the response
		   now and wait a bit.  */
		sprintf(priv.response, "OKAY");
		priv.flag |= FASTBOOT_FLAG_RESPONSE;
		fbt_handle_response();
		udelay(1000000); /* 1 sec */

#ifdef CONFIG_CMD_BOOTRK
		do_bootrk(NULL, 0, ARRAY_SIZE(bootrk), bootrk);

		FBTERR("do_bootrk() returned, trying go..\n");
#endif

		FBTINFO("Booting raw image..\n");
		do_go(NULL, 0, ARRAY_SIZE(go), go);

		FBTERR("booting failed, reset the board\n");
	}
	sprintf(priv.response, "FAILinvalid boot image");
}

/* XXX: Replace magic number & strings with macros */
static int fbt_rx_process(unsigned char *buffer, int length)
{
#ifdef CONFIG_RK_UDC
	struct usb_endpoint_instance *ep;
	char *cmdbuf;
	int clear_cmd_buf;
	u8 *data_buffer = NULL;
	if (priv.d_size) {
		ep = &endpoint_instance[1];
		if (length > priv.transfer_buffer_size) {
			FBTERR("buffer overflow when do receive\n");
			length = priv.transfer_buffer_size;
		}
		data_buffer = (u8 *)ep->rcv_urb->buffer;

		if(priv.d_bytes + length < priv.d_size){
			int buffer_length = priv.d_size - priv.d_bytes - length;
			if (buffer_length > priv.transfer_buffer_size)
				buffer_length = priv.transfer_buffer_size;
			ep->rcv_urb->actual_length = 0;
			ep->rcv_urb->buffer_length = buffer_length;
			if(priv.d_legacy)
				ep->rcv_urb->buffer += priv.transfer_buffer_size;
			else
				ep->rcv_urb->buffer = (u8 *)priv.buffer[buffer == priv.transfer_buffer];
			resume_usb(ep, 0);
			FBTDBG("buffer %p, len %x..\n", ep->rcv_urb->buffer, length);
		}
		if (!priv.d_legacy)
			board_fbt_handle_download(data_buffer, length, &priv);

		priv.d_bytes += length;

		if (priv.d_bytes < priv.d_size && !priv.d_status) {
			/* don't clear cmd buf because we've replaced it
			 * with our transfer buffer.  we'll clear it at
			 * the end of the download.
			 */
			return 0;
		}

		if (priv.d_status < 0 && priv.d_bytes < priv.d_size) {
			FBTDBG("Failed to download, d_bytes:%lld d_size:%lld, length:%d\n",
					priv.d_bytes, priv.d_size, length);
			priv.d_bytes += length;
			return 0;
		}
		if (priv.d_status < 0) {
			/* transfer error */
			strcpy(priv.response, "FAILDownload error");
		} else {
			/* transfer complete */
			strcpy(priv.response, "OKAY");
		}
		priv.d_size = 0;
		priv.flag |= FASTBOOT_FLAG_RESPONSE;

		/* restore default buffer in urb */
		ep->rcv_urb->buffer = (u8 *)ep->rcv_urb->buffer_data;
		ep->rcv_urb->buffer_length = sizeof(ep->rcv_urb->buffer_data);

		FBTDBG("downloaded %llu bytes\n", priv.d_bytes);

		/* clear the cmd buf from last time */
		return 1;
	}

	/* command */
	cmdbuf = (char *) buffer;
	clear_cmd_buf = 1;

	/* Generic failed response */
	strcpy(priv.response, "FAIL");

	cmdbuf[FASTBOOT_COMMAND_SIZE - 1] = 0;

	/* dwc_otg controller use the buffer directory, 
	 * controller dma master write 4-byte align data to buffer,
	 * may corrupt the origin data
	 */
	if(length < FASTBOOT_COMMAND_SIZE)
		cmdbuf[length] = 0;

	FBTDBG("cmdbuf = (%s)\n", cmdbuf);
	priv.executing_command = 1;

	/* %fastboot getvar: <var_name> */
	if (memcmp(cmdbuf, "getvar:", 7) == 0) {
		FBTDBG("getvar\n");
		fbt_handle_getvar(cmdbuf);
	}

	/* %fastboot oem <cmd> */
	else if (memcmp(cmdbuf, "oem ", 4) == 0) {
		FBTDBG("oem\n");
		fbt_handle_oem(cmdbuf);
	}

	/* %fastboot erase <partition_name> */
	else if (memcmp(cmdbuf, "erase:", 6) == 0) {
		FBTDBG("erase\n");
		fbt_handle_erase(cmdbuf);
	}

	/* %fastboot flash:<partition_name> */
	else if (memcmp(cmdbuf, "flash:", 6) == 0) {
		FBTDBG("flash\n");
		fbt_handle_flash(cmdbuf, 1);
		priv.pending_ptn = NULL;
	}

	/* %fastboot reboot
	 * %fastboot reboot-bootloader
	 */
	else if (memcmp(cmdbuf, "reboot", 6) == 0) {
		FBTDBG("reboot or reboot-bootloader\n");
		fbt_handle_reboot(cmdbuf);
	}

	/* %fastboot continue */
	else if (strcmp(cmdbuf, "continue") == 0) {
		FBTDBG("continue\n");
		strcpy(priv.response, "OKAY");
		priv.exit = 1;
	}

	/* %fastboot boot <kernel> [ <ramdisk> ] */
	else if (memcmp(cmdbuf, "boot", 4) == 0) {
		FBTDBG("boot\n");
		fbt_handle_boot(cmdbuf);
	}

	/* Sent as part of a '%fastboot flash <partname>' command
	 * This sends the data over with byte count:
	 * %download:<num_bytes>
	 */
	else if (memcmp(cmdbuf, "download:", 9) == 0) {
		FBTDBG("download\n");

		/* XXX: need any check for size & bytes ? */
		int d_size = simple_strtoul (cmdbuf + 9, NULL, 16);
		priv.d_bytes = 0;
		priv.d_status = 0;

		FBTDBG("starting download of %d bytes\n", d_size);

		priv.d_legacy = !priv.pending_ptn_name[0] ||
			(!strcmp(priv.pending_ptn_name, RECOVERY_NAME) ||
			 !strcmp(priv.pending_ptn_name, BOOT_NAME) ||
			 !strcmp(priv.pending_ptn_name, LOADER_NAME) ||
			 !strcmp(priv.pending_ptn_name, PARAMETER_NAME) ||
			 !strcmp(priv.pending_ptn_name, RESOURCE_NAME) ||
			 !strcmp(priv.pending_ptn_name, UBOOT_NAME));

		FBTDBG("legacy download? %d\n", priv.d_legacy);

		if (!priv.unlocked) {
			FBTERR("download: failed, device is locked\n");
			sprintf(priv.response, "FAILdevice is locked");
		} else if (d_size > CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH
				&& !priv.pending_ptn) {
			FBTERR("download large image with \"-u\" option\n");
			sprintf(priv.response, "FAILnot support \"-u\" option");
		} else if (d_size >= CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE &&
				priv.d_legacy) {
			FBTERR("%s image too large\n", priv.pending_ptn_name);
			sprintf(priv.response, "FAILdata too large");
		} else if (d_size == 0) {
			strcpy(priv.response, "FAILdata invalid size");

			/* maybe board side can handle this.
			   } else if (d_size > priv.transfer_buffer_size) {
			   d_size = 0;
			   strcpy(priv.response, "FAILdata too large");
			   */
		} else {
			priv.d_size = d_size;
			sprintf(priv.response, "DATA%08llx", priv.d_size);

			/* we will check boot/recovery's sha, so need a big buffer to recv whole image.
			 usb dwc_udc controller can receive 0x20000byte data for once */
			priv.transfer_buffer_size = USB_MAX_TRANS_SIZE;
			/*priv.d_legacy? CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE :
			  CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH;*/

			/* as an optimization, replace the builtin
			 * urb->buffer and urb->buffer_length with our
			 * own so we don't have to do extra copy.
			 */
			ep = &endpoint_instance[1];
			ep->rcv_urb->buffer = priv.transfer_buffer;
			ep->rcv_urb->buffer_length = 
				priv.transfer_buffer_size > priv.d_size? priv.d_size: 
				priv.transfer_buffer_size;
			ep->rcv_urb->actual_length = 0;

			/* don't poison the cmd buffer because
			 * we've replaced it with our
			 * transfer buffer for the download.
			 */
			clear_cmd_buf = 0;
			resume_usb(ep, 0);
		}
	}
	priv.flag |= FASTBOOT_FLAG_RESPONSE;
	priv.executing_command = 0;
	return clear_cmd_buf;
#else
	char *cmdbuf;
	int clear_cmd_buf;
	u8 *data_buffer = NULL;
	if (priv.d_size) {
		if (length > priv.transfer_buffer_size) {
			FBTERR("buffer overflow when do receive\n");
			length = priv.transfer_buffer_size;
		}
		data_buffer = buffer;

		if (priv.d_bytes + length < priv.d_size) {
			int buffer_length = priv.d_size - priv.d_bytes - length;
			if (buffer_length > priv.transfer_buffer_size)
				buffer_length = priv.transfer_buffer_size;
			if (priv.d_legacy)
				fbt_read_bulk_ep(buffer_length, (u8 *)buffer + length);
			else
				fbt_read_bulk_ep(buffer_length, priv.buffer[buffer == priv.transfer_buffer]);
			FBTDBG("buffer %p, len %x..\n", buffer, length);
		}
		if (!priv.d_legacy)
			board_fbt_handle_download(data_buffer, length, &priv);

		priv.d_bytes += length;

		if (priv.d_bytes < priv.d_size && !priv.d_status) {
			/* don't clear cmd buf because we've replaced it
			 * with our transfer buffer.  we'll clear it at
			 * the end of the download.
			 */
			return 0;
		}

		if (priv.d_status < 0 && priv.d_bytes < priv.d_size) {
			FBTDBG("Failed to download, d_bytes:%lld d_size:%lld, length:%d\n",
					priv.d_bytes, priv.d_size, length);
			priv.d_bytes += length;
			return 0;
		}
		if (priv.d_status < 0) {
			/* transfer error */
			strcpy(priv.response, "FAILDownload error");
		} else {
			/* transfer complete */
			strcpy(priv.response, "OKAY");
		}
		priv.d_size = 0;
		priv.flag |= FASTBOOT_FLAG_RESPONSE;

		FBTDBG("downloaded %llu bytes\n", priv.d_bytes);

		/* clear the cmd buf from last time */
		return 1;
	}

	/* command */
	cmdbuf = (char *)buffer;
	clear_cmd_buf = 1;

	/* Generic failed response */
	strcpy(priv.response, "FAIL");

	cmdbuf[FASTBOOT_COMMAND_SIZE - 1] = 0;

	/* dwc_otg controller use the buffer directory,
	 * controller dma master write 4-byte align data to buffer,
	 * may corrupt the origin data
	 */
	if (length < FASTBOOT_COMMAND_SIZE)
		cmdbuf[length] = 0;

	FBTDBG("cmdbuf = (%s)\n", cmdbuf);
	priv.executing_command = 1;

	/* %fastboot getvar: <var_name> */
	if (memcmp(cmdbuf, "getvar:", 7) == 0) {
		FBTDBG("getvar\n");
		fbt_handle_getvar(cmdbuf);
	}

	/* %fastboot oem <cmd> */
	else if (memcmp(cmdbuf, "oem ", 4) == 0) {
		FBTDBG("oem\n");
		fbt_handle_oem(cmdbuf);
	}

	/* %fastboot erase <partition_name> */
	else if (memcmp(cmdbuf, "erase:", 6) == 0) {
		FBTDBG("erase\n");
		fbt_handle_erase(cmdbuf);
	}

	/* %fastboot flash:<partition_name> */
	else if (memcmp(cmdbuf, "flash:", 6) == 0) {
		FBTDBG("flash\n");
		fbt_handle_flash(cmdbuf, 1);
		priv.pending_ptn = NULL;
	}

	/* %fastboot reboot
	 * %fastboot reboot-bootloader
	 */
	else if (memcmp(cmdbuf, "reboot", 6) == 0) {
		FBTDBG("reboot or reboot-bootloader\n");
		fbt_handle_reboot(cmdbuf);
	}

	/* %fastboot continue */
	else if (strcmp(cmdbuf, "continue") == 0) {
		FBTDBG("continue\n");
		strcpy(priv.response, "OKAY");
		priv.exit = 1;
	}

	/* %fastboot boot <kernel> [ <ramdisk> ] */
	else if (memcmp(cmdbuf, "boot", 4) == 0) {
		FBTDBG("boot\n");
		fbt_handle_boot(cmdbuf);
	}

	/* Sent as part of a '%fastboot flash <partname>' command
	 * This sends the data over with byte count:
	 * %download:<num_bytes>
	 */
	else if (memcmp(cmdbuf, "download:", 9) == 0) {
		FBTDBG("download\n");

		/* XXX: need any check for size & bytes ? */
		int d_size = simple_strtoul(cmdbuf + 9, NULL, 16);
		priv.d_bytes = 0;
		priv.d_status = 0;

		FBTDBG("starting download of %d bytes\n", d_size);

		priv.d_legacy = !priv.pending_ptn_name[0] ||
			 (!strcmp(priv.pending_ptn_name, RECOVERY_NAME) ||
			 !strcmp(priv.pending_ptn_name, BOOT_NAME) ||
			 !strcmp(priv.pending_ptn_name, LOADER_NAME) ||
			 !strcmp(priv.pending_ptn_name, PARAMETER_NAME) ||
			 !strcmp(priv.pending_ptn_name, RESOURCE_NAME) ||
			 !strcmp(priv.pending_ptn_name, UBOOT_NAME));

		FBTDBG("legacy download? %d\n", priv.d_legacy);

		if (!priv.unlocked) {
			FBTERR("download: failed, device is locked\n");
			sprintf(priv.response, "FAILdevice is locked");
		} else if (d_size > CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH && !priv.pending_ptn) {
			FBTERR("download large image with \"-u\" option\n");
			sprintf(priv.response, "FAILnot support \"-u\" option");
		} else if (d_size >= CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE && priv.d_legacy) {
			FBTERR("%s image too large\n", priv.pending_ptn_name);
			sprintf(priv.response, "FAILdata too large");
		} else if (d_size == 0) {
			strcpy(priv.response, "FAILdata invalid size");
			/* maybe board side can handle this.
			   } else if (d_size > priv.transfer_buffer_size) {
			   d_size = 0;
			   strcpy(priv.response, "FAILdata too large");
			   */
		} else {
			int buffer_length;
			priv.d_size = d_size;
			sprintf(priv.response, "DATA%08llx", priv.d_size);

			/* we will check boot/recovery's sha, so need a big buffer to recv whole image.
			 usb dwc_udc controller can receive 0x20000byte data for once */
			priv.transfer_buffer_size = USB_MAX_TRANS_SIZE;
			/*priv.d_legacy? CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE :
			  CONFIG_FASTBOOT_TRANSFER_BUFFER_SIZE_EACH;*/
			/* as an optimization, replace the builtin
			 * urb->buffer and urb->buffer_length with our
			 * own so we don't have to do extra copy.
			 */
			buffer_length = priv.transfer_buffer_size > priv.d_size ? priv.d_size : priv.transfer_buffer_size;

			/* don't poison the cmd buffer because
			 * we've replaced it with our
			 * transfer buffer for the download.
			 */
			clear_cmd_buf = 0;
			fbt_read_bulk_ep(buffer_length, priv.transfer_buffer);
		}
	}
	priv.flag |= FASTBOOT_FLAG_RESPONSE;
	priv.executing_command = 0;
	return clear_cmd_buf;

#endif
}

static void fbt_handle_rx(void)
{
#ifdef CONFIG_RK_UDC
	struct usb_endpoint_instance *ep = &endpoint_instance[1];

	/* XXX: Or update status field, if so,
	   "usbd_rcv_complete" [gadget/core.c] also need to be modified */
	if (ep->rcv_urb->actual_length) {
		if (fbt_rx_process((unsigned char *)ep->rcv_urb->buffer, ep->rcv_urb->actual_length)) {
			/* Poison the command buffer so there's no confusion
			 * when we receive the next one.  fastboot commands
			 * are sent w/o NULL termination so we don't want
			 * stale data in the buffer.
			 * Also, it is assumed that at the time of creation of
			 * urb it is poisoned.
			 */
			memset((void *)ep->rcv_urb->buffer, 0, FASTBOOT_COMMAND_SIZE);
			ep->rcv_urb->actual_length = 0;
			resume_usb(ep, FASTBOOT_COMMAND_SIZE);
		}
		fbt_handle_response();
	}
#else
	if (priv.rx_giveback.actual) {
		if (fbt_rx_process((unsigned char *)priv.rx_giveback.buf,
					priv.rx_giveback.actual)) {
			memset(priv.rx_giveback.buf, 0, FASTBOOT_COMMAND_SIZE);
			priv.rx_giveback.actual = 0;
			fbt_read_bulk_ep(FASTBOOT_COMMAND_SIZE, priv.rx_buffer);
		}
		fbt_handle_response();
	}
#endif
}

static void fbt_response_process(void)
{
#ifdef CONFIG_RK_UDC
	struct usb_endpoint_instance *ep = &endpoint_instance[2];
	struct urb *current_urb = NULL;
	unsigned char *dest = NULL;
	int n;

	current_urb = next_urb(device_instance, ep);
	if (!current_urb) {
		FBTERR("%s: current_urb NULL", __func__);
		return;
	}

	dest = (unsigned char *)current_urb->buffer + current_urb->actual_length;
	n = MIN(64, strlen(priv.response));
	memcpy(dest, priv.response, n);
	current_urb->actual_length += n;
	/*
	 * This FBTDBG appears to break communication when DEBUG
	 * is on, so comment it out.
	 FBTDBG("response urb length: %u\n", current_urb->actual_length);
	 */
	if (ep->last == 0)
		udc_endpoint_write(ep);
#else
	unsigned char *dest = NULL;
	int n;

	dest = (unsigned char *)priv.tx_buffer;
	n = MIN(64, strlen(priv.response));
	memcpy(dest, priv.response, n);

	if (priv.tx_giveback.status == SEND_FINISHED_OK) {
		fbt_write_bulk_ep(n, priv.tx_buffer);
	}
#endif
}

static void fbt_handle_response(void)
{
	if (priv.flag & FASTBOOT_FLAG_RESPONSE) {
		fbt_response_process();
		priv.flag &= ~FASTBOOT_FLAG_RESPONSE;
	}
}


/* command */
static int do_fastboot(cmd_tbl_t *cmdtp, int flag, int argc,
		char * const argv[])
{
	int ret;
	/* currently we don't allow restarting fastboot if you've run
	 * it before and exited to u-boot prompt.  it's possible to
	 * support, but there's are edge cases that we're not
	 * sure the answer to (e.g. do we reload the partition table
	 * or not) and it's just not a common situation so we're
	 * just saying no for now.
	 */
	if (priv.flag & FASTBOOT_FLAG_HAS_RUN) {
		FBTERR("fastboot can't be restarted\n");
		return -1;
	}
	priv.flag |= FASTBOOT_FLAG_HAS_RUN;

	FBTDBG("Starting fastboot protocol\n");
#ifdef CONFIG_RK_UDC
	fbt_init_endpoint_ptrs();

	ret = udc_init();
	if (ret < 0) {
		FBTERR("%s: MUSB UDC init failure\n", __func__);
		goto out;
	}

	fbt_init_strings();
	fbt_init_instances();

	udc_startup_events(device_instance);
	udc_connect();

	FBTINFO("fastboot initialized\n");

	priv.pending_ptn = NULL;
	priv.pending_ptn_name[0] = '\0';
	SecureBootCheck();

	while (1) {
		if (priv.configured) {
			fbt_handle_rx();
			if (priv.unlock_pending_start_time) {
				/* check if unlock pending should expire */
				if (get_timer(priv.unlock_pending_start_time) >
						(FASTBOOT_UNLOCK_TIMEOUT_SECS * 1000)) {
					FBTDBG("unlock pending expired\n");
					priv.unlock_pending_start_time = 0;
				}
			}
		}
#ifdef CONFIG_CTRLC
		priv.exit |= ctrlc();
#endif
		if (priv.exit) {
			FBTINFO("fastboot end\n");
			break;
		}
	}
	do_reset(NULL, 0, 0, NULL);
out:
#else
	struct rk_dwc3_udc_instance device;
	unsigned long elapsed;
	FBTINFO("<<Dwc3>> do_fastboot come in.\n");
	fbt_init_dwc3_udc_instance(&device);

	rk_dwc3_startup(&device);
	ret = rk_dwc3_connect();
	if (ret) {
		FBTERR("<<Dwc3>> rk_dwc3_connect failed.\n");
		goto Exit_DoFastboot;
	}
	FBTDBG("<<Dwc3>> fastboot initialized.\n");
	priv.pending_ptn = NULL;
	priv.pending_ptn_name[0] = '\0';
	SecureBootCheck();
	while (1) {
		dwc3_uboot_handle_interrupt();
		if (priv.configured) {
			fbt_handle_rx();
			if (priv.unlock_pending_start_time) {
				/* check if unlock pending should expire */
				elapsed = get_timer(priv.unlock_pending_start_time);
				if (elapsed > (FASTBOOT_UNLOCK_TIMEOUT_SECS * 1000)) {
					FBTDBG("unlock pending expired\n");
					priv.unlock_pending_start_time = 0;
				}
			}
		}
#ifdef CONFIG_CTRLC
		priv.exit |= ctrlc();
#endif
		if (priv.exit) {
			FBTINFO("fastboot end\n");
			break;
		}
	}
	do_reset(NULL, 0, 0, NULL);
Exit_DoFastboot:
	rk_dwc3_disconnect();
#endif
	return ret;
}

U_BOOT_CMD(fastboot, 1,	1, do_fastboot,
		"use USB Fastboot protocol", NULL);

#ifdef CONFIG_FASTBOOT_LOG
int fbt_log(const char *info, const int len, bool send)
{
	unsigned long space_in_log = CONFIG_FASTBOOT_LOG_SIZE - log_position - 1;
	unsigned long bytes_to_log;

	/* check if relocation is done before we can use globals */
	if (gd->flags & GD_FLG_RELOC) {
		if (len > space_in_log)
			bytes_to_log = space_in_log;
		else
			bytes_to_log = len;

		if (bytes_to_log) {
			strncpy(&log_buffer[log_position], info, bytes_to_log);
			log_position += bytes_to_log;
		}
	}

	if (!send)
		return 0;
	return fbt_send_raw_info(info, len);
}

int fbt_send_info(const char *info)
{
	return fbt_log(info, strlen(info), true);
}
#endif
